package modeles;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.util.ArrayList;

public class DES_Encrypt {

	String key;
	String message;
	CBC cbc;
	ECB ecb;
	CFB cfb;
	OFB ofb;
	
	public static String retour_decrypt = "";

	public DES_Encrypt(String key, String message) {
		this.key = key;
		this.message = message;
		cbc = new CBC(this);
		ecb = new ECB(this);
		cfb = new CFB(this);
		ofb = new OFB(this);
	}

	public static boolean verifKey(String key){
		byte[] message_byte = null;

		boolean retour = true;

		try {
			message_byte = key.getBytes("UTF8");
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("taille : "+message_byte.length);
		if (message_byte.length != 8){
			System.out.println("clé incorrect");
			retour = false;
		}
		return retour;
	}

		/**
		 * convertit bloc message en bit
		 * @return tab de bits
		 */
		public int[] doStringtoBin(String bloc_message) {	
			System.out.println("dans doStringtoBin : "+bloc_message);
			byte[] message_byte = null;
			int[] retour = null ;

			try {
				message_byte = bloc_message.getBytes("UTF8");
			} catch (UnsupportedEncodingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("taille : "+message_byte.length);

			int size = 0;
			ByteArrayBitIterable babi = new ByteArrayBitIterable(message_byte);
			for(boolean b : babi){
				size ++;
				if (b)
					System.out.print("1");
				else
					System.out.print("0");
			}
			System.out.println("");

			//si pas modulo 64, on ajoute des "a" a la fin de la chaîne (1 byte)
			if (size%64!=0){
				int times = 8-((size%64)/8);
				while (times != 0){
					bloc_message = bloc_message.concat(" ");
					times --;
				}
				try {
					message_byte = bloc_message.getBytes("UTF8");
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				babi = new ByteArrayBitIterable(message_byte);
				size = 0;
				for(boolean b : babi)
					size ++;
			}

			retour = new int[size];
			int i=0;
			for (boolean b : babi){
				if (b)
					retour[i]=1;
				else
					retour[i]=0;
				i++;
			}


			return retour;


		}

		public int[] doEncryptBloc(int[] IP_input, int[] key_bit, boolean encrypt) {
			int round = 0;
			int i=0;
			//sortie apres IP
			int[] IP_output = new int[64];
			//sortie apres IP-1
			int[] IP_1_input = new int[64];
			int[] IP_1_output = new int[64];

			//IP
			for (i=0; i<64; i++){
				IP_output[i] = IP_input[Data.getIPTable()[i]-1];
			}
			//printTab(IP_input, "input");
			//printTab(IP_output, "output");

			//Division left and right
			int[] L = new int[32];
			int[] R = new int[32];

			for(i=0; i<32; i++){
				L[i]=IP_output[i];
				R[i]=IP_output[i+32];
			}

			//printTab(L, "message left");
			//printTab(R, "message right");

			//L and R temp pour calculer
			int[] L_temp = L;
			int[] R_temp = R;

			//printTab(key_bit, "key enc1");
			//Generation des cles (16 cles de 48 bits)
			int[][] keys = generateKeys(key_bit);
			int[] K = new int [48]; //cle du round "round"

			//16 rounds

			for (round=0; round<16; round++){
				System.out.println("ENTREE DANS ROUND = "+round);
				//generate key_round
				L = R_temp; // Li = Ri-1
				if (encrypt)
					K = keys[round]; //recuperation cle du round correspondant
				else //cas decryption, cles dans l'autre sens
					K=keys[15-round];
				System.out.println("*******************************************************Calcul R**************************************");
				R = calculR(L_temp,R_temp, K); //Ri = Li-1 XOR f(Ri-1, Ki)
				//prochain round
				R_temp = R;
				L_temp = L;
			}

			//reassemblage L16R16
			for(i=0; i<32; i++){
				IP_1_input[i]=R[i];
				IP_1_input[i+32]=L[i];
			}

			printTab(IP_1_input, "apres reassemblage R16L16");

			//IP-1
			for (i=0; i<64; i++){
				IP_1_output[i] = IP_1_input[Data.getIP_1Table()[i]-1];
			}
			printTab(IP_1_output, "final IP_1_output");
			return IP_1_output;

		}

		private int[] calculR(int[] L_temp, int[] R_temp, int[] K) {
			System.out.println("calcul nouveau R");
			int[] R = new int[32]; //result
			//expansion
			int[]R_exp =new int[48];
			int i=0;
			for (i=0; i<48; i++){
				R_exp[i] = R_temp[Data.getExpansionTable()[i]-1];
			}
			//printTab(K, "cle en cours");
			//printTab(R_exp, "apres expansion");
			//XOR R_exp et Key	
			int[]R_xor = new int[48];
			R_xor = XOR(R_exp, K);
			//printTab(R_xor, "apres xor  : R_xor");

			//SBOXS
			int[] C_tot = new int[32];		
			C_tot = SBOXES(R_xor);
			//printTab(C_tot, "apres sboxes C_tots");

			//permutation 
			for (i=0; i<32; i++){
				R[i] = C_tot[Data.getPermutationTable()[i]-1];
			}
			//printTab(R, "apres permutation R");
			//XOR R apres fonction f et Li-1
			R=XOR(R, L_temp);
			//printTab(R, "apres dernier Xor entre Li-1 et resultat de f");
			return R;
		}

		private int[] SBOXES(int[] R_xor) {
			int bloc=0, i=0, j=0, deb=0, fin=6;
			//decoupage R_xor en 8 blocs B de 6 bits(b1...b6)
			int[] B = new int[6];
			int[] C = new int[4]; //apres passage sbox
			int[][] C_tot_temp = new int[8][4]; //range les C au fur et a mesure
			int[] C_tot = new int[32]; //result
			int[][] sbox_encours = new int[4][16]; //sbox a utiliser suivant numero bloc

			//pour chaque bloc
			for (bloc=0; bloc<8; bloc++){
				C = new int[4];
				//System.out.println("sboxes - bloc : "+bloc);

				//remplissage B
				for (i=deb,j=0; i<fin && j<6; i++,j++){
					B[j] = R_xor[i];
				}
				//printTab(B, "sboxes - B");
				//choix sbox
				sbox_encours = choixSbox(bloc);
				//row de sbox = b1b6
				String row_s = String.valueOf(B[0]) + String.valueOf(B[5]);
				//col de sbox = b2b3b4b5
				String col_s = "";
				for (i=1; i<5; i++){
					col_s+=String.valueOf(B[i]);
				}

				int row = Integer.parseInt(row_s, 2);
				int col = Integer.parseInt(col_s, 2);

				//recherche C dans sbox
				int c_int = sbox_encours[row][col];
				//conversion int -> bin
				String c_s = Integer.toBinaryString(c_int);
				c_s = "0000".concat(c_s);
				c_s = c_s.substring(c_s.length()-4);
				//System.out.println("row="+row+ " col="+col+" c_int="+c_int+" c_s="+c_s);

				//remplissage C
				for (i=0; i<4; i++){
					C[i] = Integer.parseInt(c_s.substring(i,i+1));
				}
				//printTab(C, "C (taille 4)");

				//remplissage C_tot_temp
				C_tot_temp[bloc] = C;

				//bloc suivant
				deb+=6; 
				fin+=6;

			}

			//remplissage C_tot
			int i_glob=0;
			for (i=0; i<8; i++){
				for(j=0; j<4; j++){
					C_tot[i_glob] = C_tot_temp[i][j];
					i_glob++;
				}
			}
			printTab(C_tot, "sortie sboxes C_tot");

			return C_tot;

		}



		private int[][] choixSbox(int bloc){
			int[][] sbox_encours = new int[4][16];
			switch(bloc){
			case 0: 
				sbox_encours = Data.getSbox1();
				break;
			case 1: 
				sbox_encours = Data.getSbox2();
				break;
			case 2: 
				sbox_encours = Data.getSbox3();
				break;
			case 3: 
				sbox_encours = Data.getSbox4();
				break;
			case 4: 
				sbox_encours = Data.getSbox5();
				break;
			case 5: 
				sbox_encours = Data.getSbox6();
				break;
			case 6: 
				sbox_encours = Data.getSbox7();
				break;
			case 7: 
				sbox_encours = Data.getSbox8();
				break;
			}
			return sbox_encours;

		}

		public int[] XOR(int[] left, int[] right) {
			int[]xor =new int[left.length];
			int i_glob=0;
			int i=0;
			for(i=0; i<left.length; i++){
				if(left[i]==right[i]){
					xor[i_glob]=0;
				}
				else{
					xor[i_glob]=1;
				}
				i_glob++;
			}
			return xor;
		}

		private int[][] generateKeys(int[] key_bit) {
			//System.out.println("ENTREE DANS GENERATION KEYS");
			int[][] keys = new int[16][48];
			int[] key = new int[56]; //apres PC1
			int[] L_key = new int[28]; //Left de k_temp
			int[] R_key = new int[28]; //right de k_temp
			int[] k = new int[48]; //apres PC2
			int i, round;

			//permuted choice 1
			for (i=0; i<56; i++){
				key[i] = key_bit[Data.getPC_1Table()[i]-1];
			}
			//printTab(key, "key after PC_1");

			//division left et right original
			for(i=0; i<28; i++){
				L_key[i]=key[i];
				R_key[i]=key[i+28];
			}

			//calcul des cles pour chaque round
			for (round=0; round<16; round++){
				k= new int[48];
				//System.out.println("DANS GENERATION ROUND : "+round);
				//left shift L et R
				//printTab(L_key, "L_key avant shift");
				//printTab(R_key, "R_key avant shift");
				R_key = leftShift(R_key, round);
				L_key = leftShift(L_key, round);
				//printTab(L_key, "L_key apres shift");
				//printTab(R_key, "R_key apres shift");

				//rassemblement left et right
				for(i=0; i<28; i++){
					key[i]=L_key[i];
					key[i+28]=R_key[i];
				}
				//printTab(key, "reassemblage key");

				//permuted choice 2
				for (i=0; i<48; i++){
					k[i] = key[Data.getPC_2Table()[i]-1];
				}
				//printTab(k, "key after PC2");
				keys[round] = k;
			}

			//		for (round=0; round<16; round++){
			//			printTab(keys[round], "keys numero "+round);
			//		}
			return keys;
		}

		private int[] leftShift(int[] demi_key, int round) {
			int temp0=demi_key[0];
			int i=0;

			//shift de 1
			for(i=1; i<28; i++){    
				demi_key[i-1]=demi_key[i];
			}
			demi_key[27]=temp0;

			//shift de 2 selon tab left_shift
			if (Data.getLeft_shift()[round]==2){
				temp0=demi_key[0];
				for(i=1; i<28; i++){    
					demi_key[i-1]=demi_key[i];
				}
				demi_key[27]=temp0;
			}

			return demi_key;
		}

		public int[] doEncryption(String mode) {

			//verif taille
			//message= verifSize();
			System.out.println("message : "+message);
			System.out.println("key : "+key);

			//conversion key en bit
			int[] key_bit = doStringtoBin(key);
			//conversion message en bit (%64=0)
			int[] message_bit = doStringtoBin(message);
			int[] res = null;
			if (mode.equals("ECB"))
				res = ecb.encrypt(key_bit, message_bit, true);
			else if (mode.equals("CBC"))
				res = cbc.encrypt(key_bit, message_bit);
			else if (mode.equals("CFB"))
				res = cfb.encrypt(key_bit, message_bit);
			else if (mode.equals("OFB"))
				res = ofb.encrypt(key_bit, message_bit);

			return res;

		}



		public void doDecryption(int[] message_bit, String mode){
			int[] key_bit = doStringtoBin(key);
			if (mode.equals("ECB"))
				ecb.encrypt(key_bit, message_bit, false);
			else if (mode.equals("CBC"))
				cbc.decrypt(key_bit, message_bit);
			else if (mode.equals("CFB"))
				cfb.decrypt(key_bit, message_bit);
			else if (mode.equals("OFB"))
				ofb.decrypt(key_bit, message_bit);
		}

		public void doDecryptionString(String message, String mode) {
			//conversion key en bit
			int[] key_bit = doStringtoBin(key);
			//conversion message en bit (%64=0)
			int[] message_bit = doStringtoBin(message);
			if (mode.equals("ECB"))
				ecb.encrypt(key_bit, message_bit, false);
			else if (mode.equals("CBC"))
				cbc.decrypt(key_bit, message_bit);
			else if (mode.equals("CFB"))
				cbc.decrypt(key_bit, message_bit);
			else if (mode.equals("OFB"))
				cbc.decrypt(key_bit, message_bit);
		}

		public String binToString(int[] tab, int nbBlocs){
			byte[] test_byte = new byte[tab.length/8];
			String bin_ligne = "";
			int deb_ligne = 0;

			int i = 0;
			for (i=0; deb_ligne<tab.length; i++){
				bin_ligne = "";
				for (int j=deb_ligne; j<deb_ligne+8; j++){	
					bin_ligne += String.valueOf(tab[j]); 
				}
				int _int = Integer.parseInt(bin_ligne, 2);
				test_byte[i] = (byte) _int;
				deb_ligne+=8;
			}
			
			String msg = "";
			try {
				msg = new String(test_byte, "UTF8");
			} catch (UnsupportedEncodingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("msg crypt : " +msg);
			return msg;

		}

		public void printTab(int[] arr, String nom) {
			System.out.println("\nprint tab "+nom);
			int taille = arr.length;
			int mod = 8;
			if(taille == 56 || taille==28) mod=7;
			else if (taille == 48) mod=6;
			else if (taille == 32) mod=4;
			for(int i=0;i<arr.length ;i++) {
				if (i%mod==0 && i!=0)
					System.out.print("\n");
				System.out.print(arr[i]+ " ");
			}
			System.out.println("\nfin print tab\n");
		}




	}
